package com.ltu.config;


import com.alibaba.fastjson.JSON;
import com.ltu.base.State;
import com.ltu.model.response.base.CodeDataResp;
import com.ltu.util.exception.ApiException;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.exceptions.IbatisException;
import org.apache.shiro.ShiroException;
import org.apache.shiro.authc.AuthenticationException;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.MethodParameter;
import org.springframework.core.NestedRuntimeException;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.ui.Model;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
 
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.sql.SQLException;
import java.util.stream.Collectors;
 
/**
  * 全局异常处理、controller返回格式统一处理
  * @author ruochen
  * @date 2020/12/24
  */
@SuppressWarnings({"unused"})
@Slf4j
@ControllerAdvice(basePackages = {"com.ltu.controller"})
public class CustomerAdviceController implements ResponseBodyAdvice<Object>{
 
    /**
      * 应用到所有@RequestMapping注解方法，在其执行之前初始化数据绑定器
      */
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        //do-nothing
    }
    
    /**
      * 捕捉shiro的异常
      * @author ruochen
      * @param e :
      * @return com.cetccloud.base.CodeDataResp
      * @date 2020/12/24 10:21
      */
    @ResponseBody
    @ExceptionHandler(ShiroException.class)
    public CodeDataResp handle401(ShiroException e) {
        e.printStackTrace();
        State  s= new State(401,e.getMessage()); 
        return CodeDataResp.valueOfFailed( s );
    }
    
    /**
     * 捕捉shiro的异常
     * @author ruochen
     * @param e :
     * @return com.cetccloud.base.CodeDataResp
     * @date 2020/12/24 10:21
     */
    @ResponseBody
    @ExceptionHandler(AuthenticationException.class)
    public CodeDataResp handle401(AuthenticationException e) {
    	log.error(e.getMessage());
    	e.printStackTrace();
    	State  s= new State(500,"登录失败用户名或密码错误"); 
    	return CodeDataResp.valueOfFailed( s );
    }
 
    /**
      * 捕捉ApiException
      * @author ruochen
      */
    @ResponseBody
    @ExceptionHandler(ApiException.class)
    public CodeDataResp myError(ApiException e) {
        log.error("系统错误ApiException：{}",e.getMessage());
        return CodeDataResp.valueOfFailed(e.getMessage());
    }
 
    /**
      * 处理请求参数格式错误 @RequestBody上validate失败后抛出的异常是MethodArgumentNotValidException异常
      * @author ruochen
      */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public CodeDataResp methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::
                getDefaultMessage).collect(Collectors.joining(","));
        return CodeDataResp.valueOfFailed(e.getMessage());
    }
 
    /**
      * 处理Get请求中 使用@Valid 验证路径中请求实体校验失败后抛出的异常
      * @author ruochen
    
      */
    @ExceptionHandler(BindException.class)
    @ResponseBody
    public CodeDataResp bindExceptionHandler(BindException e) {
        String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::
                getDefaultMessage).collect(Collectors.joining(","));
        return CodeDataResp.valueOfFailed(message);
    }
 
    /**
      * 处理请求参数格式错误 @RequestParam上validate失败后抛出的异常是ConstraintViolationException
      * @author ruochen
      */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public CodeDataResp constraintViolationExceptionHandler(ConstraintViolationException e) {
        return  CodeDataResp.valueOfFailed( e.getMessage());
    }
 
    /**
      * 捕捉其他所有异常
      * @author ruochen
      * @param request :
      */
    @ResponseBody
    @ExceptionHandler(Throwable.class)
    public CodeDataResp globalException(HttpServletRequest request, Throwable ex) {
        //mybatis异常，与数据异常处理
        if(ex instanceof NestedRuntimeException || ex instanceof IbatisException || ex instanceof SQLException){
            log.error("系统错误-数据库错误：", ex);
            return  CodeDataResp.valueOfFailed("系统数据错误");
        }
        ex.printStackTrace();
        return  CodeDataResp.valueOfFailed(ex.getMessage());
    }
 
    /**
      * 获取Http访问的状态码
      * @author ruochen
      */
    private HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
        return HttpStatus.valueOf(statusCode);
    }
 
    /*--------------------------------- 以下全局处理，统一返回格式 ------------------------*/
 
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }
 
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        final String returnTypeName = methodParameter.getParameterType().getName();
        //如果无返回值
        if("void".equals(returnTypeName)){
            return  CodeDataResp.valueOfSuccessEmptyData();
        }
 
        //如果返回值是String类型，单独处理，否则会出现类型转换错误
        if("java.lang.String".equals(returnTypeName)){
            return o;
        }
        //如果返回类型是资源类型，不处理
        if(o instanceof Resource){
            return o;
        }
        //如果头信息返回类型不是application/json,不处理
        if(!mediaType.includes(MediaType.APPLICATION_JSON)){
            return o;
        }
        //如果用户自己返回已经定义为统一返回的格式，不处理
        if(returnTypeName.endsWith(".CodeDataResp") || returnTypeName.endsWith(".CodeResp")){
            return o;
        }
 
        //其他情况，统一处理为统一的格式
        return CodeDataResp.valueOfSuccess(o);
    }
    }